package com.njbw.minio.utils;

import com.njbw.minio.config.MinioProperties;
import com.njbw.minio.entity.CreateMultipartUploadResult;
import com.njbw.minio.entity.FileMultipartUploadReq;
import com.njbw.minio.entity.MinioFile;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

public class MinioTemplate {

    /**
     * Minio 客户端
     */
    private final CustomMinioClient customMinioClient;
    private final MinioProperties minioProperties;

    private static final Logger log = LoggerFactory.getLogger(MinioTemplate.class);

    @Autowired
    public MinioTemplate(CustomMinioClient minioClient, MinioProperties minioProperties) {
        this.customMinioClient = minioClient;
        this.minioProperties = minioProperties;
    }

    /**
     * 查询所有存储桶
     */
    public List<Bucket> listBuckets() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        return customMinioClient.listBuckets();
    }

    /**
     * 桶是否存在
     */
    public boolean bucketExists(String bucketName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        return customMinioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 创建存储桶
     */
    public void makeBucket(String bucketName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        if (!bucketExists(bucketName)) {
            customMinioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 根据字节上传文件对象
     *
     * @param bytes
     * @param path
     */
    public void putObjectByBytes(byte[] bytes, String path) {
        try {
//           上传文件
            PutObjectArgs build = PutObjectArgs.builder()
                    .bucket(minioProperties.getBucket())
                    .object(path).stream(new ByteArrayInputStream(bytes), bytes.length, -1)
                    .build();
            customMinioClient.putObject(build);
        } catch (Exception e) {
            log.error("minio上传文件异常： {}", e.getMessage(), e);
        }
    }

    /**
     * 上传文件
     *
     * @param inputStream      流
     * @param originalFileName 原始文件名
     * @param bucketName       桶名
     * @return OssFile
     */
    public MinioFile putObject(InputStream inputStream, String bucketName, String originalFileName) {
        String uuidFileName = generateMinioUuidFileName(originalFileName);
        try {
            if (StringUtils.isBlank(bucketName)) {
                bucketName = minioProperties.getBucket();
            }
            customMinioClient.putObject(
                    PutObjectArgs.builder().bucket(bucketName).object(uuidFileName).stream(
                                    inputStream, inputStream.available(), -1)
                            .build());
            return new MinioFile(uuidFileName, originalFileName);
        } catch (Exception e) {
            log.error("minio上传文件异常： {}", e.getMessage(), e);
            throw new RuntimeException(e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("minio上传文件关闭异常失败：{}", e.getMessage(), e);
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * 批量删除对象文件
     *
     * @param deleteObjects
     */
    public void removeObjects(List<DeleteObject> deleteObjects) {
        Iterable<io.minio.Result<DeleteError>> results = customMinioClient.removeObjects(RemoveObjectsArgs.builder()
                .bucket(minioProperties.getBucket())
                .objects(deleteObjects)
                .build());
        try {
            for (io.minio.Result<DeleteError> result : results) {
                DeleteError error = result.get();
                log.error("Error in deleting object " + error.objectName() + "; " + error.message());
            }
        } catch (Exception e) {
            log.error("minio 删除文件异常: {}", e.getMessage(), e);
        }
    }

    /**
     * 删除目录
     * @param delDir
     */
    public void delByDir(String delDir) {
        try {
            Iterable<Result<Item>> results = listObjects(delDir);
            for (Result<Item> result : results) {
                boolean dir = result.get().isDir();
                String objectName = result.get().objectName();
                if (dir) {
                    this.delByDir(objectName);
                }
                customMinioClient.removeObject(
                        RemoveObjectArgs.builder().bucket(minioProperties.getBucket()).object(objectName).build());
            }
        } catch (Exception e) {
            log.error("minio删除文件异常 {}", e.getMessage(), e);
        }
    }

    /**
     * 查询桶的对象信息
     *
     * @param bucketName 桶名
     * @param recursive  是否递归查询
     */
    public Iterable<Result<Item>> listObjects(String bucketName, boolean recursive) {
        return customMinioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).recursive(recursive).build());
    }

    public Iterable<Result<Item>> listObjects(String dir) {
        return customMinioClient.listObjects(
                ListObjectsArgs.builder().bucket(minioProperties.getBucket())
                        .prefix(dir)
                        .build());
    }

    /**
     * 上传分片请求，返回 uploadId
     */
    public CreateMultipartUploadResponse uploadId(String bucketName, String objectName) throws ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, IOException, InvalidKeyException, XmlParserException, InvalidResponseException, InternalException {
        return customMinioClient.createMultipartUpload(bucketName, null, objectName, null, null);
    }

    /**
     * 返回临时带签名，过期时间 1 天，Get 请求方式的访问 URL
     *
     * @param bucketName 桶名
     * @param minioFilePath Minio 文件路径
     */
    public String getPreSignedObjectUrl(String bucketName, String minioFilePath, Map<String, String> queryParams) {
        try {
            return customMinioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.PUT)
                            .bucket(bucketName)
                            .object(minioFilePath)
                            .expiry(60 * 60 * 24)
                            .extraQueryParams(queryParams)
                            .build()
            );
        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidResponseException |
                 InvalidKeyException | IOException | NoSuchAlgorithmException | XmlParserException |
                 ServerException e) {
            log.error("返回临时签名失败：{}", e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 创建 uploadId，并申请临时分片文件存放地址
     */
    public CreateMultipartUploadResult createMultipartUpload(FileMultipartUploadReq req) throws ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, IOException, InvalidKeyException, XmlParserException, InvalidResponseException, InternalException {
        req.checkNull();

        // 根据文件名创建签名
        Map<String, Object> result = new HashMap<>();
        // 获取 uploadId
        CreateMultipartUploadResponse resp = this.uploadId(minioProperties.getBucket(), req.getObjectName());
        String uploadId = resp.result().uploadId();
        // 请求 Minio 服务，获取每个分块带签名的上传 URL
        Map<String, String> reqParams = new HashMap<>();
        reqParams.put("uploadId", uploadId);

        // 循环分块数，从 1 开始，Minio 存储服务定义分片索引却是从 0 开始的
        for (int i = 1; i <= req.getChunkSize(); i ++) {
            reqParams.put("partNumber", String.valueOf(i));
            // 获取URL,主要这里前端上传的时候，要传递二进制流，而不是file
            String uploadUrl = this.getPreSignedObjectUrl(minioProperties.getBucket(), req.getObjectName(), reqParams);
            // 添加到集合
            result.put("chunk_" + (i - 1), uploadUrl);
        }
        return new CreateMultipartUploadResult.Builder()
                .uploadId(uploadId)
                .chunks(result)
                .build();
    }

    /**
     * 完成分片上传，执行合并文件
     *
     * @param bucketName       存储桶
     * @param objectName       对象名
     * @param uploadId         上传ID
     * @param parts            分片
     */
    private ObjectWriteResponse completeMultipartUpload(String bucketName, String objectName, String uploadId, Part[] parts) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
        return customMinioClient.completeMultipartUpload(bucketName, null, objectName, uploadId, parts, null, null);
    }

    public void completeMultipartUpload(String fileName, String uploadId) {
        if (fileName == null || uploadId == null) {
            throw new RuntimeException("请检查您的请求参数");
        }
        try {
            // 查询所有分片信息
            ListPartsResponse partResult = this.listMultipart(minioProperties.getBucket(),
                    fileName,
                    uploadId
            );

            Part[] parts = new Part[partResult.result().partList().size()];
            int partNumber = 1;
            for (Part part : partResult.result().partList()) {
                parts[partNumber - 1] = new Part(partNumber, part.etag());
                partNumber++;
            }
            // 合并分片
            this.completeMultipartUpload(
                    minioProperties.getBucket(),
                    fileName,
                    uploadId,
                    parts
            );
        } catch (IllegalArgumentException e) {
            log.error("合并分片文件异常： {} ", e.toString());
            throw new RuntimeException("请检查您的分片文件是否全部上传完成");
        } catch (Exception e) {
            log.error("合并分片文件异常： {} ", e.toString());
            throw new RuntimeException("合并文件异常，请联系管理员");
        }
    }

    /**
     * 生成 Minio uuid 文件名
     */
    public String generateMinioUuidFileName(String originalFilename) {
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        return LocalDateTime.now(ZoneId.systemDefault()).format(DateTimeFormatter.ISO_LOCAL_DATE) + "/" + UUID.randomUUID() + suffix;
    }

    /**
     * 查询分片数据
     *
     * @param bucketName       存储桶
     * @param objectName       对象名
     * @param uploadId         上传ID
     */
    public ListPartsResponse listMultipart(String bucketName, String objectName, String uploadId) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
        return customMinioClient.listMultipart(bucketName, null, objectName, null, null, uploadId, null, null);
    }

    /**
     * 复制文件
     */
    public void copyFile(String path, String oldReplaceVal, String newReplaceVal) {
        Iterable<Result<Item>> results = listObjects(path);
        try {
            for (Result<Item> result : results) {
                Item item = result.get();
                String objectedName = item.objectName();
                if (item.isDir()) {
                    copyFile(objectedName, oldReplaceVal, newReplaceVal);
                } else {
                    // 执行复制操作
                    String newObject = objectedName.replace(oldReplaceVal, newReplaceVal);
                    copyFile(objectedName, newObject);
                }
            }
        } catch (Exception e) {
            log.error("minio 复制对象异常 {}", e.getMessage(), e);
            throw new RuntimeException("备份文件异常");
        }
    }

    /**
     * 复制对象
     *
     * @param oldPath
     * @param newPath
     */
    public void copyFile(String oldPath, String newPath) {
        try {
            customMinioClient.copyObject(
                    CopyObjectArgs.builder()
                            .bucket(minioProperties.getBucket())
                            .object(newPath)
                            .source(
                                    CopySource.builder()
                                            .bucket(minioProperties.getBucket())
                                            .object(oldPath)
                                            .build())
                            .build());
        } catch (Exception e) {
            log.error("minio 复制对象异常 {}", e.getMessage(), e);
            throw new RuntimeException("备份文件异常");
        }
    }

    /**
     * 下载文件
     */
    public InputStream download(String objectName) throws Exception {
        return customMinioClient.getObject(GetObjectArgs.builder()
                .bucket(minioProperties.getBucket())
                .object(objectName)
                .build());
    }

    /**
     * 预览文件地址
     */
    public String getPreviewFileUrl(String objectName) {
        return minioProperties.getEndpoint() + "/" + minioProperties.getBucket() + "/" + objectName;
    }
}
